#ifndef GDBMANAGER_H
#define GDBMANAGER_H

/*
    This module contains serveral concrete implementation
    of GDBMIClient. Each one is responsible for one aspect
    in debug.

    Each client will handle the commands the user issues
    and display the result to user in UI, which involves
    sending proper request to gdb, and parsing gdb response,
    and updating the UI accordingly.
*/

#include "gdbmi.h"
#include "varhelper.h"

class MainWindow;
class QTreeWidget;
class QListWidget;
class QTreeWidgetItem;
class VarTreeManager;
class QTextBrowser;
class QLineEdit;

#ifdef Q_OS_WIN
class Win32DebugThread;
#endif

class GDBMIDefaultClient : public GDBMIAsyncClient
{
public:
    GDBMIDefaultClient (GDBMI * pThread, MainWindow * mainWindow);

    virtual void onExit();
    virtual void onStarted() {}
    virtual void onStopped(STOPREASON reason);
    virtual void onAsyncRecord(MIAsyncRecord * asyncRecord);
    virtual void onStreamRecord(MIStreamRecord * streamRecord);
    virtual bool onResultRecord(MIResultRecord * resultRecord);
    virtual void onResultParseErr(int token);

    bool startDebug(const QString & workDirectory, const QString & target);

protected:
    void handleSignal(MIResult * result);
};

typedef std::pair<int, int> BKPTPAIR;

class GDBMIExecClient : public GDBMIClient
{
public:
    GDBMIExecClient (GDBMI * pThread);

    virtual void onStopped (STOPREASON reason);
    virtual void onStarted ()    {}
    virtual void onExit ();
    virtual bool onResultRecord (MIResultRecord * resultRecord);
    virtual void onResultParseErr(int token);

    bool setBreakpoint (const QString & filename, int line, bool set = true,
                        QString condition = QString(), bool toCond = false);
    void run (bool stopAtMain, QString args);
    void stop ();
     void pause ();
    void resume ();
    void stepOut ();
    void stepOver ();
    void runtoCursor(QString fileName,int line);
    void stepInto ();

    int getUnnecessaryBreakpoints(int line,int bkptno,std::list<BKPTPAIR> &bkpt_list);
    bool breakpointExist(int line);
protected:


    std::list<BKPTPAIR> m_bkpts;
    bool                m_stopped;
    bool                isGdbStopped;
    bool                isTempBrInsert;
    int                 tempBrline;
    QString             tempBrFileName;

#ifdef Q_OS_WIN
    Win32DebugThread    *m_helperThread;
#endif
};

class GDBMIStackClient : public GDBMIClient
{

public:
    GDBMIStackClient (GDBMI * pThread, QListWidget * stackList)
        : GDBMIClient (pThread), m_stackList (stackList), m_refresh (true)
    {stack_fullfilename = "";stack_funcname = "";stack_line = stack_depth = -1;}

    virtual void onStarted ()    {}
    virtual void onStopped (STOPREASON reason);
    virtual void onExit ();
    virtual bool onResultRecord (MIResultRecord * resultRecord);
    virtual void onResultParseErr(int token);
    //bool isInMain();

protected:
    typedef std::pair<QString, QString>    ARGPAIR;
    typedef std::list<ARGPAIR>            ARGLIST;

    typedef struct tagFrame
    {
        QString m_func;
        QString m_file;
        int     m_line;
        int     m_level;

        ARGLIST m_args;
    }Frame;

    Frame   readFrame(MIResult * result);
    void    readFrameArgs(MIResult * result, Frame * frame);
    QString formatFrame(Frame * frame);
    QString gdbstrToUnicode(QString src);

    std::list<Frame>    m_frames;
    QListWidget         *m_stackList;
    int                 m_refresh;
    int                 m_currentStackDepth;
    QString             stack_fullfilename;
    QString             stack_funcname;
    int                 stack_line;
    int                 stack_depth;
};

/*
    The mechanism behind GDBMIVariableClient is somewhat complex,
    so it needs a bit more documentation.

    As its name indicates, GDBMIVariableClient is responsible for
    handling the local and watch variables of current stack context
    of target under debug. Two considerations lead to the complexity
    of this guy. The first is to delay the variable read until user
    actually make it to be displayed, since some variables(such as
    array and large struct) consume much time to read its contents
    while user may be not intrested in that. The second is to display
    the watch variable correctly in the stack context, which is not
    very easy due to the scope rule in programming language (or because
    gdb do NOT provide a convenient and efficient way to do this).

    The current implementation is as follows: when the target stops
    (just like encounter with breakpoint), GDBMIVariableClient issues
    "-stack-list-locals" to obtain a list of local variables, which
    contains variable name and simple value (no value for compound type,
    such as array, struct). If user want to view the variable of
    compound type, GDBMIVariableClient issues "-var-create" and
    "-var-list-children" to expand these values on demand. The expanded
    values will be buffered for efficiency, therefore, the operation
    to expand takes place only once.

    For the watch part, GDBMIVariableClient first search the local list
    mentioned above, if some one matched, its value will be displayed
    in the watch, otherwise, GDBMIVariableClient will try to find the
    variable objects in gdb /mi for the expressions not matched in local
    list. If found, its value will be adopted, otherwise, GDBMIVariableClient
    will try to create a variable object. The reason to create a variable
    object is to obtain the value for global variable. If the expression
    in watch is exactly refer to a global one, the attempt to create a
    variable object will succeed, otherwise, fail. Hence, GDBMIVariableClient
    will only try to create a variable object for one expression once, never
    again.
*/
class GDBMIVariableClient : public GDBMIClient
{
public:
    GDBMIVariableClient (GDBMI *pThread,
        QTreeWidget *localTree, QTreeWidget *watchTree);
    virtual ~GDBMIVariableClient ();

    virtual void onStopped (STOPREASON reason);
    virtual void onStarted () {}
    virtual void onExit ();
    virtual bool onResultRecord (MIResultRecord * resultRecord);
    virtual void onResultParseErr(int token);

    bool addWatch (const QString & expression);
    void delWatch (int idx);

    void updateVariableObject (const QString & name, bool create);
    void updateVariableObject (const QString & name, bool create,int,int);
    void deleteVariableObject (const QString & name);
    void myRetranslateUi();
    void setIsPascal(bool isPascal);

protected:
    /*
        WatchItem maintains the mapping between the
        watch expression and variable object in gdb /mi

        For local variable, the name of variable object
        is the same as the name of local variable name.
        For watch expression, we let gdb /mi to choose
        a name for me to avoid the confliction.
    */
    class WatchItem
    {
    public:
        WatchItem (const QString & expr) : m_expr (expr), m_var (0)    {}

        QString        m_name;    // name used in gdb /mi
        QString        m_expr;    // name specified by user
        MIVariable    * m_var;
    };

    struct ArrayStart{
        QString m_name;
        int m_start;
        int m_arrayLength;
    };

    typedef std::pair<QString, int>                RequestPair;
    typedef std::list<RequestPair>                RequestList;
    typedef std::list<QTreeWidgetItem *>        ItemSet;
    typedef std::list<WatchItem>                WatchItemList;
    typedef std::pair<int, MIVariable *>            VRequestPair;
    typedef std::list<VRequestPair>                VRequestList;

    void    updateVariable (MIValue * value);
    int     readVariable (MIResult * result, MIVariable * var);
    void    sendWatchRequest (const QString & expr);
    int     indexAtWatchTable (const QString & name);
    void    parseChildrenList (MIVariable * var, MIResult * result);
    void    parseChildrenList (MIVariable * var, MIResult * result,int start,int end);

    void    updateLocalTree ();
    void    updateWatchTree ();
    void    clearWatchTreeContent ();
    void    updateTreeItem (QTreeWidgetItem * item, MIVariable * var, bool child);
    void    updateWatchItem (WatchItem * w);
    void    updateWatchItem (QTreeWidgetItem * item);


    MIVariable    *findVariable (const QString & name);


    QTreeWidgetItem    *findTreeItem (
        QTreeWidget *widget, const QString & name, bool onlyTop = false);
    bool    findTreeItem (QTreeWidgetItem * item, const QString & name, ItemSet & set);
    void    updateDelayedItem (MIVariable * var);

    RequestList::iterator findRequest (int token);

    void    markAll ();
    void    truncMarked ();

    WatchItem    * findWatchItemByName (const QString & name);
    WatchItem    * findWatchItemByExpr (const QString & expr);

    QTreeWidget            * m_localTree;
    QTreeWidget            * m_watchTree;

    RequestList            m_requestList;
    VRequestList            m_vRequestList;

    MIVariable *        m_root;
    WatchItemList        m_watches;

    VarTreeManager        * m_manager;

    MIVariable *        m_temp;            //the variable is for the func(findVariable)
    std::list<ArrayStart>    m_arrayStartNum;//use to record the startnum & arraylength of these arrays whose length > 200

    bool m_IsPascal;
};

inline void GDBMIExecClient::stop ()
{
    if (m_stopped && !isGdbStopped) {
        sendRequest ("-gdb-exit");
        isGdbStopped = true;
    }
    else
        m_pGDBMI->stopDebug ();
}

inline void GDBMIExecClient::pause ()
{
    sendRequest("Contol-C");
    /*if (m_stopped)
        sendRequest ("-gdb-exit");
    else
        m_pGDBMI->stopDebug ();*/
}

inline void GDBMIExecClient::resume ()
{
    m_stopped = false;
    sendRequest ("-exec-continue");
}

inline void GDBMIExecClient::stepOut ()
{
    m_stopped = false;
    sendRequest ("-exec-finish");
}

inline void GDBMIExecClient::stepOver ()
{
    m_stopped = false;
    sendRequest ("-exec-next");
}

inline void GDBMIExecClient::runtoCursor(QString fileName,int line)
{
    //qDebug("GDBMIExecClient::runtoCursor");
    isTempBrInsert = true;
    tempBrline = line;
    tempBrFileName = fileName;
    int pos = fileName.lastIndexOf('/',-1);
    if (pos != -1)
        fileName = fileName.right(fileName.length() - pos - 1);
    sendRequest(QString("-break-insert %1:%2").arg(fileName).arg(line));
    resume();
}

inline void GDBMIExecClient::stepInto ()
{
    m_stopped = false;
    sendRequest ("-exec-step");
}

inline void GDBMIVariableClient::sendWatchRequest (const QString & expr)
{
    sendRequest (QString ("-var-create %1 * %2").arg (expr, expr));
}

inline GDBMIVariableClient::WatchItem * GDBMIVariableClient::findWatchItemByName (
    const QString & name)
{
    for (WatchItemList::iterator it = m_watches.begin ();
        it != m_watches.end ();
        ++ it)
    {
        //qDebug(it->m_name.toLatin1().constData());
        if (it->m_name == name) {
            return & (* it);
        }
    }

    return 0;
}

inline GDBMIVariableClient::WatchItem * GDBMIVariableClient::findWatchItemByExpr (
    const QString & expr)
{
    if (! m_IsPascal) {
        for (WatchItemList::iterator it = m_watches.begin ();
            it != m_watches.end ();
            ++ it)
        {
            if (it->m_expr == expr)
                return & (* it);
        }
    } else {
        // add by porky zhai 20090106
        // reason is that gdb will change all var of pascal to capital letter.
        for (WatchItemList::iterator it = m_watches.begin ();
            it != m_watches.end ();
            ++ it) {
            if (!QString::compare(it->m_expr.toUpper(),expr.toUpper())) {
                //qDebug("ho ho ,it is here!");
                return & (* it);
            }
        }
    }
    return 0;
}

inline MIVariable * GDBMIVariableClient::findVariable (const QString & name)
{
    MIVariable * var = m_root->findVariable (name);
    if (! var) {
        //qDebug(name.section('.',0,0).toLatin1().constData());
        //WatchItem * item = findWatchItemByName (name.section ('.', 0, 0));
        WatchItem * item  = 0;
        QStringList list = name.split(".");
        QString str_name = list[0];
        int i = 0;
        for (i = 0;i < list.count();++i) {
            item = findWatchItemByName(str_name);
            if (item) {break;}
            if (i+1 < list.count()) str_name += "." + list[i+1];
        }

        if (item && item->m_var) {
            QString watchName = name.section ('.', i+1);
            if (! watchName.isEmpty ()) {
                m_temp = item->m_var->findVariable (watchName);
                return m_temp;
            } else {
                m_temp = item->m_var;
                return m_temp;
            }
        }

    }
    m_temp = var;
    return var;
}

inline GDBMIVariableClient::RequestList::iterator GDBMIVariableClient::findRequest (int token)
{
    for (RequestList::iterator it = m_requestList.begin ();
        it != m_requestList.end ();
        ++ it) {
            //qDebug("%d\t%s\n",it->second,it->first.toLatin1().constData());
            if (it->second == token) {
                //m_requestList.erase(m_requestList.(),it);
                return it;
            }
    }

    return m_requestList.end ();
}


class GDBMIMemoryClient : public GDBMIClient
{
public:
    GDBMIMemoryClient (GDBMI *pThread, QTextBrowser *memBrowser, QLineEdit *addr, QLineEdit *bytes)
        : GDBMIClient (pThread), m_memBrowser (memBrowser), m_addrLine (addr), m_bytesLine (bytes)
    {}

    virtual void onStarted ();
    virtual void onStopped (STOPREASON reason);
    virtual void onExit ();
    virtual bool onResultRecord (MIResultRecord * resultRecord);
    virtual void onResultParseErr (int token);

    bool showMemory ();

protected:

    QTextBrowser    *m_memBrowser;
    QLineEdit       *m_addrLine;
    QLineEdit       *m_bytesLine;
    QString         m_begin;
    QString         m_offset;
    QString         m_end;
    QString         m_contents;
    int             m_showBytes;
};

#endif

